package cn.qingyun.gis.modules.init.utils;

import cn.qingyun.gis.modules.init.system.entity.SftpConfig;
import com.jcraft.jsch.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.Arrays;

@Component
@Data
@Slf4j
public class SftpUtils {

    /**
     * SFTP 配置字典
     */
    public static final String SFTP_CONFIG = "SftpConfig";

    // 设置第一次登陆的时候提示，可选值：(ask | yes | no)
    private static final String SESSION_CONFIG_STRICT_HOST_KEY_CHECKING = "StrictHostKeyChecking";

    // 设置第一次登陆的时候提示，可选值：(ask | yes | no)
    private static final String STRICT_HOST_KEY_CHECKING_VALUE = "no";

    /**
     * SFTP
     */
    private static final String CHANNEL_SFTP = "sftp";

    /**
     * SFTP
     */
    private static final Integer CONNECT_TIME_OUT = 15000;

    /**
     * 创建SFTP连接
     *
     * @return
     * @throws Exception
     */
    public static ChannelSftp createSftp(SftpConfig config) throws Exception {
        log.debug("Try to connect sftp[" + config.getUsername() + "@" + config.getHost() + "], use password[" + config.getPassword() + "]");
        Integer sessionTimeout = CONNECT_TIME_OUT;
        Integer channelTimeout = CONNECT_TIME_OUT;
        if (config.getSessionConnectTimeout() != null && config.getSessionConnectTimeout().compareTo(0) > 0) {
            sessionTimeout = config.getSessionConnectTimeout();
        }
        if (config.getChannelConnectedTimeout() != null && config.getSessionConnectTimeout().compareTo(0) > 0) {
            channelTimeout = config.getChannelConnectedTimeout();
        }

        JSch jsch = new JSch();
        log.debug("Session connected to {}.", config.getHost());
        Session session = createSession(jsch, config.getHost(), config.getUsername(), config.getPort());
        session.setPassword(config.getPassword());
        session.connect(sessionTimeout);

        Channel channel = session.openChannel(CHANNEL_SFTP);
        channel.connect(channelTimeout);

        log.debug("Channel created to {}.", config.getHost());

        return (ChannelSftp) channel;
    }


    /**
     * 将输入流上传到SFTP服务器，作为文件
     *
     * @param directory    上传到SFTP服务器的路径
     * @param sftpFileName 上传到SFTP服务器后的文件名
     * @param input        输入流
     * @throws SftpException
     */
    public static void upload(String directory, String sftpFileName, InputStream input, ChannelSftp sftp) throws SftpException {
        long start = System.currentTimeMillis();
        try {
            //如果文件夹不存在，则创建文件夹
            if (sftp.ls(directory) == null) {
                sftp.mkdir(directory);
            }
            //切换到指定文件夹
            sftp.cd(directory);
        } catch (SftpException e) {
            //创建不存在的文件夹，并切换到文件夹
            sftp.mkdir(directory);
            sftp.cd(directory);
        }
        sftp.put(input, sftpFileName);
        log.debug("文件上传成功！！ 耗时：{}ms", (System.currentTimeMillis() - start));
    }

    /**
     * 下载文件转换为InputStream
     *
     * @param directory 文件目录
     * @param fileName  文件名
     * @param sftp      sftp 链接
     * @throws SftpException
     */
    public static InputStream download4InputStream(String directory, String fileName, ChannelSftp sftp) throws SftpException {
        long start = System.currentTimeMillis();
        if (sftpFilesExist(directory, fileName, sftp)) {
            String filePath = directory + "/" + fileName;
            log.debug("文件下载成功！！ 耗时：{}ms", (System.currentTimeMillis() - start));
            return sftp.get(filePath);
        }
        return null;
    }

    /**
     * 下载文件转换为InputStream
     *
     * @param directory 文件目录
     * @param fileName  文件名
     * @param sftp      sftp 链接
     * @throws SftpException
     */
    public static File downloadFile(String directory, String fileName, String tempPath, ChannelSftp sftp) {
        long start = System.currentTimeMillis();
        OutputStream outputStream = null;
        try {
            if (sftpFilesExist(directory, fileName, sftp)) {
                String filePath = directory + "/" + fileName;
                // 定义缓冲路径
                File file = new File(tempPath + fileName);
                // 调用 sftp 服务，将文件获取至缓冲路径中
                outputStream = Files.newOutputStream(file.toPath());
                sftp.get(filePath, outputStream);
                log.debug("文件下载成功！！ 耗时：{}ms", (System.currentTimeMillis() - start));
                return file;
            }
        } catch (Exception error) {
            log.error("文件下载异常！！ 耗时：{}ms", (System.currentTimeMillis() - start));
            log.error("文件下载异常：{}", error.getLocalizedMessage(), error);
        } finally {
            try {
                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (IOException e) {
                outputStream = null;
            }
        }
        return null;
    }

    /**
     * 判断目录下是否存在指定文件
     *
     * @param directory
     * @param fileName
     * @param sftp
     * @return
     */
    public static boolean sftpFilesExist(String directory, String fileName, ChannelSftp sftp) {
        boolean result = true;
        try {
            sftp.cd(directory);
            sftp.stat(fileName); //返回文件信息  -rwxrwxrwx 0 0 182000000 Wed Sep 23 14:44:49 CST 2020
        } catch (Exception e) {
            log.debug("sftp {}目录下不存在该文件{}", directory, fileName + e.getMessage());
            result = false;
        }
        return result;
    }

    /**
     * 创建session
     *
     * @param jsch
     * @param host
     * @param username
     * @param port
     * @return
     * @throws Exception
     */
    private static Session createSession(JSch jsch, String host, String username, Integer port) throws Exception {
        Session session;

        if (port <= 0) {
            session = jsch.getSession(username, host);
        } else {
            session = jsch.getSession(username, host, port);
        }

        if (session == null) {
            throw new Exception(host + " session is null");
        }
        session.setConfig(SESSION_CONFIG_STRICT_HOST_KEY_CHECKING, STRICT_HOST_KEY_CHECKING_VALUE);
        return session;
    }

    /**
     * 关闭连接
     *
     * @param sftp
     */
    public static void disconnect(ChannelSftp sftp) {
        try {
            // 断开与SFTP服务器的连接
            if (sftp != null && sftp.isConnected()) {
                sftp.disconnect();
            }
            if (sftp != null && sftp.getSession() != null) {
                sftp.getSession().disconnect();
            }
        } catch (JSchException e1) {
            log.error("disconnect, directory:{}", e1.getLocalizedMessage(), e1);
            e1.printStackTrace();
        }
    }

    /**
     * 创建文件夹
     *
     * @param dirPath
     * @param sftp
     * @return
     */
    public static boolean createDirs(String dirPath, ChannelSftp sftp) {
        if (dirPath != null && !dirPath.isEmpty() && sftp != null) {
            String[] dirs = Arrays.stream(dirPath.split("/"))
                    .filter(StringUtils::isNotBlank)
                    .toArray(String[]::new);
            log.debug("createDirs createDirs is: {}", dirPath);
            for (String dir : dirs) {
                try {
                    sftp.cd(dir);
                    log.debug("Change directory {}", dir);
                } catch (Exception e) {
                    try {
                        sftp.mkdir(dir);
                        log.debug("Create directory {}", dir);
                    } catch (SftpException e1) {
                        log.error("Create directory failure, directory:{}", dir, e1);
                        e1.printStackTrace();
                    }
                    try {
                        sftp.cd(dir);
                        log.debug("Change directory {}", dir);
                    } catch (SftpException e1) {
                        log.error("Change directory failure, directory:{}", dir, e1);
                        e1.printStackTrace();
                    }
                }
            }
            return true;
        }
        return false;
    }
}
